package adapters

import (
	"context"
	"ddd-practice/domain"
	"errors"
	"math/rand"
	"time"
)

var _ domain.Repository = (*ProductMemoryRepository)(nil)

var ProductMemoryTable = make([]*ProductMemoryModel, 0, 100)

type ProductMemoryRepository struct {
	ObjectConverter
}

func NewProductMemoryRepository() ProductMemoryRepository {
	return ProductMemoryRepository{}
}

func (p ProductMemoryRepository) PutOnShelves(ctx context.Context, product *domain.Product) error {

	productModel, err := p.DomainObjectToModel(product)
	if err != nil {
		return err
	}
	ProductMemoryTable = append(ProductMemoryTable, productModel)

	return nil
}

func (p ProductMemoryRepository) GetProduct(ctx context.Context, id int) (*domain.Product, error) {

	productModel, err := p.getProduct(ctx, id)
	if err != nil {
		return nil, err
	}

	productDO, err := p.ModelToDomainObject(productModel)
	if err != nil {
		return nil, err
	}

	return productDO, nil
}

func (p ProductMemoryRepository) GetPage(ctx context.Context, pageIndex int, pageSize int) (list *[]domain.Product, count int, err error) {
	productPage := make([]domain.Product, 0, pageSize)
	list = &productPage

	if pageIndex < 0 {
		pageIndex = 1
	}
	if pageSize < 0 {
		pageSize = 10
	}

	offset := (pageIndex - 1) * pageSize
	if offset < 0 {
		offset = 0
	}

	for k, model := range ProductMemoryTable {

		if k+1 > offset && len(*list) <= pageSize {
			if model.IsDelete() {
				continue
			}
			productDO, err := p.ModelToDomainObject(model)
			if err != nil {
				return nil, 0, err
			}
			(*list) = append(*list, *productDO)
		}

	}

	return list, len(ProductMemoryTable), nil
}

func (p ProductMemoryRepository) UpdateProduct(ctx context.Context, product *domain.Product) error {
	productModel, err := p.getProduct(ctx, product.Id())
	if err != nil {
		return err
	}

	if productModel.Description != product.Description() {
		productModel.Description = product.Description()
	}

	if productModel.Stock != product.Stock() {
		productModel.Stock = product.Stock()
	}

	if productModel.Price != product.Price() {
		productModel.Price = product.Price()
	}

	if productModel.Image != product.Image() {
		productModel.Image = product.Image()
	}

	if product.StatusEqual(domain.Deleted) {
		productModel.DeleteTime = time.Now()
	}

	productModel.UpdateTime = time.Now()

	return nil
}

func (p ProductMemoryRepository) PutOffShelves(ctx context.Context, id int) error {
	productModel, err := p.getProduct(ctx, id)
	if err != nil {
		return err
	}

	productModel.DeleteTime = time.Now()

	return nil
}

func (p ProductMemoryRepository) getProduct(ctx context.Context, id int) (*ProductMemoryModel, error) {
	var productModel *ProductMemoryModel

	for _, model := range ProductMemoryTable {
		if model.Id == id {
			productModel = model
			break
		}
	}

	if productModel == nil {
		return nil, errors.New("record not found")
	}

	return productModel, nil
}

// 对象转换器
type ObjectConverter struct {
}

func (e ObjectConverter) DomainObjectToModel(productDO *domain.Product) (*ProductMemoryModel, error) {

	rand.Seed(time.Now().UnixNano())

	model := &ProductMemoryModel{
		Id:          int(time.Now().Unix()),
		Name:        productDO.Name(),
		Price:       productDO.Price(),
		Description: productDO.Description(),
		Stock:       productDO.Stock(),
		Image:       productDO.Image(),
		CreateTime:  time.Now(),
		UpdateTime:  time.Now(),
	}

	if productDO.StatusEqual(domain.Deleted) {
		return nil, errors.New("Domain Object status is invalid!")
	}

	return model, nil
}

func (e ObjectConverter) ModelToDomainObject(productModel *ProductMemoryModel) (*domain.Product, error) {

	var status = domain.Normal

	if !productModel.DeleteTime.Equal(time.Time{}) {
		status = domain.Deleted
	}

	productDO, err := domain.NewProduct(
		productModel.Id,
		productModel.Name,
		productModel.Price,
		productModel.Stock,
		productModel.Description,
		productModel.Image,
		status,
	)
	if err != nil {
		return nil, err
	}
	return productDO, nil
}
